#!/usr/bin/env python
#
#       Interfaces.py
#       
#       Copyright 2009 Ben Davis <its.hip@gmail.com>, Pete Lord <mr.petelord@gmail.com>, Robin Hayes <bonzairob@hotmail.co.uk>
#       
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, or
#       (at your option) any later version.
#       
#       This program is distributed in the hope that it will be useful,
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#       GNU General Public License for more details.
#       
#       You should have received a copy of the GNU General Public License
#       along with this program; if not, write to the Free Software
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#       MA 02110-1301, USA.

import os
import pygame
import string

import Events
import Options
import ToServer

from Client import Client
client = Client()

pygame.font.init()

LEFT = TOP = 0
CENTRE = CENTER = 1
RIGHT = BOTTOM = 2

class Vertical(list):
    def __init__(self, parent, background=(0, 0, 0, 0), align=LEFT, padding=5):
        list.__init__(self)
        self.parent = parent
        self.background = background
        self.rect = pygame.Rect(0, 0, 0, 0)
        self.align = align
        self.padding = padding
        
    def update(self):
        for widget in self:
            widget.update()
    
    def draw(self):
        # Initialise a new rect
        self.rect.size = (self.padding, self.padding)
        # Calculate our rect width and height
        for widget in self:
            if widget.rect.width + self.padding * 2 > self.rect.width:
                self.rect.width = widget.rect.width + self.padding * 2
            self.rect.height += widget.rect.height + self.padding
        # Create a new surface for the container
        self.surface = pygame.Surface(self.rect.size).convert_alpha()
        self.surface.fill(self.background)
        # Keep track of the current x position for the next widget
        current = self.padding
        # Define where widgets need to go
        for widget in self:
            widget.rect.top = current
            if self.align == LEFT:
                widget.rect.left = self.padding
            if self.align == CENTRE:
                widget.rect.centerx = self.rect.width / 2
            if self.align == RIGHT:
                widget.rect.right = self.rect.width - self.padding
            surface, position = widget.draw()
            self.surface.blit(surface, position)
            current += position.height + self.padding
        return self.surface, self.rect
    
    def hovering(self, pos):
        x, y = pos
        x -= self.rect.x
        y -= self.rect.y
        pos = x, y
        for widget in self:
            hovering = widget.hovering(pos)
            if hovering is not None:
                return hovering
    
    def clicking(self, pos):
        x, y = pos
        x -= self.rect.x
        y -= self.rect.y
        pos = x, y
        for widget in self:
            clicking = widget.clicking(pos)
            if clicking:
                return clicking

class Horizontal(Vertical):
    def draw(self):
        # Initialise a new rect
        self.rect.size = (self.padding, self.padding)
        # Calculate our rect width and height
        for widget in self:
            if widget.rect.height + self.padding * 2 > self.rect.height:
                self.rect.height = widget.rect.height + self.padding * 2
            self.rect.width += widget.rect.width + self.padding
        # Create a new surface for the container
        self.surface = pygame.Surface(self.rect.size).convert_alpha()
        self.surface.fill(self.background)
        # Keep track of the current x position for the next widget
        current = self.padding
        # Define where widgets need to go
        for widget in self:
            widget.rect.left = current
            if self.align == TOP:
                widget.rect.top = self.padding
            if self.align == CENTRE:
                widget.rect.centery = self.rect.height / 2
            if self.align == BOTTOM:
                widget.rect.bottom = self.rect.height - self.padding
            surface, position = widget.draw()
            self.surface.blit(surface, position)
            current += position.width + self.padding
        return self.surface, self.rect
        
class Absolutes(Vertical):
    def draw(self):
        # Initialise a new rect
        self.rect.size = (0, 0)
        # Calculate our rect width and height
        for widget in self:
            if widget.rect.right > self.rect.width:
                self.rect.width = widget.rect.right
            if widget.rect.bottom > self.rect.height:
                self.rect.height = widget.rect.bottom
        # Create a new surface for the container
        self.surface = pygame.Surface(self.rect.size).convert_alpha()
        self.surface.fill(self.background)
        for widget in self:
            surface, position = widget.draw()
            self.surface.blit(surface, position)
        return self.surface, self.rect
        
class Screen(Vertical):
    """Container which sizes to parent element and centres its children, drawing one on top of the other"""
    def draw(self):
        # Initialise a new rect
        self.rect.size = self.parent.rect.size
        self.rect.topleft = (0, 0)
        # Create a new surface for the container
        self.surface = pygame.Surface(self.rect.size).convert_alpha()
        self.surface.fill(self.background)
        for widget in self:
            # Centre the widget
            widget.rect.center = self.rect.center
            surface, position = widget.draw()
            self.surface.blit(surface, position)
        return self.surface, self.rect
        

class ShipInfoContainer(Horizontal):
    def __init__(self, parent, ship):
        Horizontal.__init__(self, parent, (0, 0, 0, 100), align=CENTRE)
        self.ship = ship
        self.append(ShipInfo(self, ship))
        if ship == client.game.selected:
            if ship.player == client.game.players.turn:
                if ship.player == client.game.player.key():
                    self.append(Vertical(self))
                    # Add new Weapon widgets for the currently selected ship
                    self[-1].append(Weapon(self[-1], client.graphics, ship, 0, client.server))
                    self[-1].append(Weapon(self[-1], client.graphics, ship, 1, client.server))
        self.draw()
        self.update()
    
    def update(self):
        x, y = self.ship.screen_location()
        self.rect.topleft = (x - (self.rect.width - Options.TILE_WIDTH[Options.ZOOM_LEVEL]) / 2, y - self.rect.height)
        Horizontal.update(self)

class MenuContainer(Screen):
    def __init__(self, parent):
        Screen.__init__(self, parent, (0, 0, 0, 100))
        self.append(SplashBG(self))
        self.events = Events.Events()
        self.main()
                
    def clicking(self, pos):
        x, y = pos
        x -= self.rect.x
        y -= self.rect.y
        pos = x, y
        for widget in reversed(self):
            clicking = widget.clicking(pos)
            if clicking:
                return clicking
        return self
        
    def hovering(self, pos):
        x, y = pos
        x -= self.rect.x
        y -= self.rect.y
        pos = x, y
        for widget in reversed(self):
            hovering = widget.hovering(pos)
            if hovering is not None:
                return hovering
        return self
        
    def close(self, *args):
        self.parent.remove(self)
        return True
    
    def main(self, *args):
        self.events.unbind(type=pygame.KEYDOWN, key=pygame.K_ESCAPE)
        try:
            self.remove(self.child)
        except AttributeError:
            pass
        self.child = Vertical(self, align=CENTRE, padding=50)
        self.child.append(HostButton(self.child))
        self.child.append(JoinButton(self.child))
        self.append(self.child)
        client.focus(self)
        if client.game is not None:
            self.events.bind(self.close, type=pygame.KEYDOWN, key=pygame.K_ESCAPE)
        return True
        
    def join(self, *args):
        self.events.unbind(type=pygame.KEYDOWN, key=pygame.K_ESCAPE)
        self.remove(self.child)
        self.child = JoinDialog(self)
        self.append(self.child)
        client.focus(self.child[1])
        self.events.bind(self.main, type=pygame.KEYDOWN, key=pygame.K_ESCAPE)
        return True
        
class JoinDialog(Vertical):
    def __init__(self, parent):
        Vertical.__init__(self, parent, background=(0, 0, 0, 100), align=CENTRE, padding=10)
        self.append(Text(self, "Type the IP or host you wish to join"))
        self.append(TextInputReturn(self, self.connect))
        self[1].esc = self.parent.main
        
    def connect(self, text):
        # Extract host/port from text
        if text.find(' ') != -1:
            host, port = text.split(' ')[:2]
            try:
                port = int(port)
            except ValueError:
                host, port = text, 7059
        else:
            host, port = text, 7059
        client.join(host, port)
    
    def clicking(self, pos):
        clicking = Vertical.clicking(self, pos)
        if clicking:
            return clicking
        if self.rect.collidepoint(pos):
            client.focus(self[1])
        

class Widget(object):
    """Baseclass for widgets"""
    
    font = pygame.font.Font(pygame.font.match_font('georgia'), 14)
    
    def __init__(self, parent, rect):
        self.parent = parent
        self.rect = rect
    
    def clicking(self, pos):
        if self.rect.collidepoint(pos):
            return self.click(pos)
    
    def click(self, pos):
        """Widget.click should return True if it doesn't grab focus but
        self if it does"""
        return
        
    def draw(self):
        return self.surface, self.rect
        
    def hovering(self, pos):
        # If pos is within rect
        if self.rect.collidepoint(pos):
            # Do stuff
            self.hover(pos)
            return self
        # If pos isn't within rect
        return
        
    def hover(self, pos):
        """Run when the widget is hovered over"""
        return
    
    def update(self):
        return
        
class Text(Widget):
    """Simple widget which renders text"""
    def __init__(self, parent, text):
        self.text = text
        self.parent = parent
        self.render()
        
    def render(self):
        self.surface = self.font.render(self.text, True, (255, 255, 255))
        self.rect = self.surface.get_rect()

class TextInput(Text):
    history = []
    def __init__(self, parent):
        self.place = 0
        self.cursor = 0
        self.events = Events.Events()
        self.events.bind(self.event, type=pygame.KEYDOWN)
        Text.__init__(self, parent, "")
        
    def render(self):
        text = self.text[:self.cursor] + "|" + self.text[self.cursor:]
        self.surface = self.font.render(text, True, (255, 255, 255))
        self.rect = self.surface.get_rect()
    
    def click(self, pos):
        return self
    
    def enter(self):
        return
    
    def esc(self):
        return
    
    def event(self, event):
        #if pygame.key.name(event.key) in string.printable:
        if pygame.key.name(event.key) in string.letters + string.digits + "'.,/-" or event.key == pygame.K_SPACE:
            char = chr(event.key)
            for mod in (pygame.KMOD_LSHIFT, pygame.KMOD_RSHIFT, pygame.KMOD_SHIFT, pygame.KMOD_CAPS):
                if event.mod & mod != 0:
                    char = char.upper()
                    if char in string.digits:
                        char = u')!@\u00A3$%^&*('[int(char)]
                    if char == '/':
                        char = '?'
            self.text = self.text[:self.cursor] + char + self.text[self.cursor:]
            self.cursor += 1
            self.render()
        elif event.key == pygame.K_BACKSPACE:
            #self.text = self.text[:-1]
            self.text = self.text[:self.cursor - 1] + self.text[self.cursor:]
            if self.cursor > 0:
                self.cursor -= 1
            self.render()
        elif event.key == pygame.K_RETURN:
            self.enter()
        elif event.key == pygame.K_ESCAPE:
            self.esc()
        elif event.key == pygame.K_UP:
            self.place -= 1
            try:
                self.text = self.history[self.place]
                self.cursor = len(self.text)
                self.render()
            except:
                self.place += 1
        elif event.key == pygame.K_DOWN:
            self.place += 1
            if self.place < 0:
                self.text = self.history[self.place]
                self.cursor = len(self.text)
                self.render()
            else:
                self.place = 0
                self.cursor = 0
                self.text = ""
                self.render()
        elif event.key == pygame.K_LEFT:
            if self.cursor > 0:
                self.cursor -= 1
                self.render()
        elif event.key == pygame.K_RIGHT:
            if self.cursor < len(self.text):
                self.cursor += 1
                self.render()
        else:
            return False
        return True

class MessageInput(TextInput):
    history = []
    def __init__(self, parent, server):
        TextInput.__init__(self, parent)
        self.server = server
    
    def enter(self):
        # Don't send empty messages
        if len(self.text) > 0:
            self.server.send(ToServer.Say(self.text))
            self.history.append(self.text)
        self.esc()
    
    def esc(self):
        self.parent.parent.remove(self.parent)
        self.parent.remove(self)
        
class TextInputReturn(TextInput):
    def __init__(self, parent, function):
        TextInput.__init__(self, parent)
        self.function = function
    
    def enter(self):
        self.function(self.text)

class MessageBox(Widget):
    """Simple messagebox widget which disappears when clicked"""
    def __init__(self, parent, message, place):
        self.message = message
        self.rect = pygame.Rect(place, (0, 0))
        self.render()
        Widget.__init__(self, parent, pygame.Rect(place, self.surface.get_size()))
            
    def click(self, pos):
        self.parent.remove(self)

    def render(self):
        padding = 10
        render = self.font.render(self.message, True, (255, 255, 255))
        width, height = render.get_size()
        width += padding * 2
        height += padding * 2
        self.surface = pygame.Surface((width, height)).convert_alpha()
        self.surface.fill((0, 0, 0, 100))
        self.surface.blit(render, (padding, padding))
        self.rect.width, self.rect.height = width, height
        
class Tooltip(MessageBox):
    def hovering(self, pos):
        return False

class TimedBox(MessageBox):
    def __init__(self, parent, message):
        MessageBox.__init__(self, parent, message, (-5, -5))
        self.timeout = 0
        for widget in parent:
            if widget != self:
                if isinstance(widget, TimedBox):
                    widget.rect.move_ip(0, -self.rect.height - 5)
    
    def update(self):
        # If we've reached our final hour
        if self.timeout == Options.MESSAGEBOX_TIMEOUT:
            # Goodbye!
            self.parent.remove(self)
        self.timeout += 1
        
class TurnBox(Widget):
    def __init__(self, parent):
        self.textures = []
        for i in range(1, 5):
            self.textures.append(client.graphics[os.path.join("Data", "UI", "p" + str(i) + "turn.png")])
        Widget.__init__(self, parent, pygame.Rect((5, 5), self.textures[0].get_size()))
        
    def click(self, pos):
        # End turn (next player)
        client.server.send(ToServer.EndTurn())
        return
        
    def hover(self, pos):
        """Run when the widget is hovered over"""
        return
    
    def draw(self):
        return self.textures[(client.game.players[client.game.players.turn].number - client.game.player.number) % len(client.game.players)], self.rect
        
class Weapon(Widget):
    def __init__(self, parent, graphics, ship, weapon, server):
        x, y = ship.shadow_location()
        self.surface = graphics[os.path.join("Data", "UI", "Cannon" + chr(65 + weapon) + ".png")]
        Widget.__init__(self, parent, pygame.Rect((0, 0), self.surface.get_size()))
        self.ship = ship
        self.weapon = weapon
        self.server = server
    
    def hover(self, pos):
        """Run when the widget is hovered over"""
        # Set the tiles to draw
        client.game.targets = self.ship.weapons[self.weapon].targets
    
    def click(self, pos):
        self.server.send(ToServer.Fire(self.ship, self.weapon))
        return True
        
    def update(self):
        x, y = self.ship.shadow_location()
        self.rect.topleft = (x + self.weapon * (self.rect.width + 5), y + Options.TILE_HEIGHT[Options.ZOOM_LEVEL])

class ShipInfo(Widget):
    """Simple widget to display info on the selected ship"""
    def __init__(self, parent, ship):
        self.ship = ship
        self.padding = 10
        self.update()
        Widget.__init__(self, parent, self.rect)

    def hovering(self, pos):
        # return None to allow click-through
        return

    def update(self):
        try:
            info = self.info()
            renders = []
            width, height = 0, 0
            for line in info:
                renders.append(self.font.render(line, True, (255, 255, 255)))
                if renders[-1].get_width() > width:
                    width = renders[-1].get_width()
                height += renders[-1].get_height()
            width += self.padding * 2
            height += self.padding * 2
            self.surface = pygame.Surface((width, height)).convert_alpha()
            self.surface.fill((0, 0, 0, 0))
            y = self.padding
            for render in renders:
                self.surface.blit(render, ((width - render.get_width()) / 2, y))
                y += render.get_height()
            self.rect = pygame.Rect(0, 0, width, height)
        except:
            self.parent.remove(self)
    
    def info(self):
        for player in client.game.players.values():
            if self.ship.key() in player.ships:
                if player == client.game.player:
                    name = "You"
                else:
                    name = player.name
        return ["Class: " + str(self.ship.__class__.__name__),
                "Player: " + name,
                "",
                "SU: " + str(self.ship.steam) + "/" + str(self.ship.steam_max) + "/+" + str(self.ship.steam_prod),
                "SI: " + str(self.ship.health) + "/" + str(self.ship.health_max),
                #"SI: " + str(int(float(self.ship.health) * 100 / self.ship.health_max)) + "%",
               ]

class Compass(Widget):
    def __init__(self, parent):
        self.surface = client.graphics[os.path.join("Data", "UI", "Compass.png")]
        Widget.__init__(self, parent, pygame.Rect((-5, 5), self.surface.get_size()))
        
    def draw(self):
        return self.surface, self.rect

    def hovering(self, pos):
        return None

class SplashBG(Widget):
    '''General gackground, used for all menus. Yes, Ratchet is the lazy.'''
    def __init__(self, parent):
        splash = client.graphics[os.path.join("Data", "UI", "splash.jpg")]
        width, height = splash.get_size()
        if client.rect.width < width:
            height = height * client.rect.width / width
            width = client.rect.width
        # Easier just to scale by width. Height later, perhaps.
        
        self.surface = pygame.transform.smoothscale(splash, (width, height))
        Widget.__init__(self, parent, pygame.Rect((0, 0), (width, height)))
        
    def click(self, pos):
        return False
        
    def draw(self):
        return self.surface, self.rect

    def hovering(self, pos):
        return None
        
class MenuButton(Widget):
    """Menu button superclass"""
    def __init__(self, parent, bitmap):
        self.surface = client.graphics[os.path.join("Data", "UI", bitmap)]
        Widget.__init__(self, parent, pygame.Rect((0, 0), self.surface.get_size()))
        self.surfaces = [(self.surface, self.surface.get_size())]
        brace = client.graphics[os.path.join("Data", "UI", "brace.png")]
        # Create a new rect to size the 'hover' surface
        hover_rect = pygame.Rect(0, 0, 0, 0)
        # Calculate the height
        if brace.get_height() > self.surface.get_height():
            hover_rect.height = brace.get_height()
        else:
            hover_rect.height = self.surface.get_height()
        # Calculate the width
        hover_rect.width = brace.get_width() * 2 + 50 + self.surface.get_width()
        # Create a new surface for the render
        hover_surface = pygame.Surface(hover_rect.size).convert_alpha()
        hover_surface.fill((0, 0, 0, 0))
        # Render the button and braces
        hover_surface.blit(brace, (0, (hover_rect.height - brace.get_height()) / 2))
        hover_surface.blit(self.surface, (25 + brace.get_width(), (hover_rect.height - self.surface.get_height()) / 2))
        hover_surface.blit(pygame.transform.flip(brace, True, False), (50 + brace.get_width() + self.surface.get_width(), (hover_rect.height - brace.get_height()) / 2))
        self.surfaces.append((hover_surface, hover_rect.size))
        self.hovered = False
    
    #def hover(self, pos):
        #if not self.hovered:
        #    self.hovered = True
        #    self.surface, self.rect.size = self.surfaces[self.hovered]
            
    #def update(self):
        #if self.hover:
        #    self.hovered = False
        #    self.surface, self.rect.size = self.surfaces[self.hovered]
            
    def draw(self):
        return self.surface, self.rect
        
class Dismiss(Widget):
    '''GO AWAY'''
    def __init__(self, parent):
        self.surface = client.graphics[os.path.join("Data", "UI", "Ram.png")]
        Widget.__init__(self, parent, pygame.Rect((200, 200), self.surface.get_size()))
        
    def click(self, pos):
        # Get rid of menus, all of them.
        self.parent.esc()
        
    def hover(self, pos):
        # extra bit like 'close?'
        return
    
    def draw(self):
        return self.surface, self.rect
        
class HostButton(MenuButton):
    '''I liek to hozt plz.'''
    def __init__(self, parent):
        MenuButton.__init__(self, parent, "host.png")
        
    def click(self, pos):
        # Do nothing until Host menu is implemented
        return
        
class JoinButton(MenuButton):
    '''I liek to join plz.'''
    def __init__(self, parent):
        MenuButton.__init__(self, parent, "join.png")
        
    def click(self, pos):
        self.parent.parent.join()
        return
    

